#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 18-May-09 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.IO;
using System.Text;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

using Thn;
using Thn.Application.Logging;
using Thn.Interface.Vcl.Themes;
#endregion

namespace Thn.Interface.Vcl
{
    /// <summary>
    /// 
    /// </summary>
    [ToolboxItem(true)]
    public class LogPanel : ListBox, ILogListener
    {
        #region Fields
        int colTime = 1;
        int colDesc = 0;
        int colMethod = 2;
        int colType = 3;

        object mSyncRoot = new object();
        AddItemHandler mAddHandler = null;
        int mMaxLogs = 100;
        volatile bool mHasScope = false;
        /// <summary>
        /// Cache of open scope by Id.
        /// <para>key   : scope</para>
        /// <para>value : ListboxItem</para>
        /// </summary>
        Hashtable mScopeById = new Hashtable();
        ListBoxItem mCurrent = null;
        #endregion
        private ContextMenuStrip mnuContext;
        private IContainer components;
        private ToolStripMenuItem mnuStop;
        private ToolStripSeparator toolStripMenuItem1;
        private ToolStripMenuItem mnuClear;
        private ToolStripSeparator toolStripMenuItem2;
        private ToolStripMenuItem mnuSave;

        #region Properties

        #region Max Logs
        /// <summary>
        /// Gets/Sets maximum number of log entries to retain.
        /// </summary>
        [Browsable(true), DefaultValue(100), Category("Behavior"), Description("Maximum number of log entries to retain")]
        public int MaxLogs
        {
            get { return mMaxLogs; }
            set { mMaxLogs = value; }
        }
        #endregion

        #region Is Listening
        /// <summary>
        /// Whether this control is listening to log events.
        /// </summary>
        volatile bool mIsListening = true;
        /// <summary>
        /// Checks whether this control is listening to log events.
        /// </summary>
        public bool IsListening
        {
            get { return mIsListening; }
        }
        #endregion

        #endregion

        #region Populate Columns
        void PopulateColumns()
        {
            Columns.Clear();

            Columns.Add("Description", 300);
            colDesc = 0;

            Columns.Add("Time", 100);
            Columns[colTime].Alignment = ContentAlignment.MiddleCenter;
            colTime = 1;
        }
        #endregion

        #region On Paint
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            base.OnPaint(e);

            if (DesignMode)
            {
                e.Graphics.DrawString("Note: this log panel only works in runtime mode", Font, Brushes.DarkGray, 5, 5);
            }
        }
        #endregion

        #region Populate Row
        void PopulateRow(ListBoxItem r, LogEvent log)
        {
            ITheme theme = EffectiveTheme;

            //time
            DateTime t = log.Time;
            r[colTime] = string.Format("{0:00}:{1:00}:{2:00}.{3:000}", t.Hour, t.Minute, t.Second, t.Millisecond);

            //description
            r[colDesc] = log.Description;

            //value
            r.Value = log;

            //set icon
            if (log is ErrorLog) r.Image = theme.FindImage(string.Format("{0}", IconStyle.Error));
            else if (log is WarningLog) r.Image = theme.FindImage(string.Format("{0}", IconStyle.Warning));
            else if (log is InformationLog) r.Image = theme.FindImage(string.Format("{0}", IconStyle.Information));
            else if (log is DebugLog) r.Image = theme.FindImage(string.Format("{0}", IconStyle.Debug));
            else r.Image = theme.FindImage(string.Format("{0}", IconStyle.Star));
        }
        #endregion

        #region Add Item
        void AddItem(ListBoxItem item)
        {
            if (InvokeRequired) this.Invoke(mAddHandler, item);
            else ProcessAddItem(item);
        }

        void ProcessAddItem(ListBoxItem item)
        {
            if (Items.Count < mMaxLogs)
            {//still not exceed the maximum threshold, add it

                Items.Add(item);
                SelectedIndex = Items.Count - 1;
            }
            else
            {//threshold exceeded, shift current items up and set the item as last item

                Items.RemoveAt(0);
                Items.Add(item);
            }
        }
        #endregion

        #region Save To File

        #region Save To File - No Name
        /// <summary>
        /// This is a convenient method to ask user where to save the file, and save it.
        /// </summary>
        public void SaveToFile()
        {
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = " Save to file";
            dlg.OverwritePrompt = true;

            DateTime t = DateTime.Now;
            dlg.FileName = string.Format("Log {0}-{1}-{2} {3}-{4}-{5}.txt", t.Year, t.Month, t.Day, t.Hour, t.Minute, t.Second);
            dlg.DefaultExt = "*.txt";

            if (dlg.ShowDialog() == DialogResult.OK)
            {
                SaveToFile(dlg.FileName);                
            }
            #region Defensive tracing
            else
            {
                //user did not accept writing
            }
            #endregion
        }
        #endregion

        #region Save To File - Given Name
        /// <summary>
        /// Write log history to file.
        /// </summary>
        public void SaveToFile(string filename)
        {
            Exception error = null;
            lock (mSyncRoot)
            {
                StreamWriter writer = null;
                try
                {
                    writer = new StreamWriter(filename);
                    WriteList(this.Items, writer, 0);
                }
                catch (Exception ex)
                {
                    error = ex;
                }
                finally
                {
                    if (writer != null)
                    {
                        writer.Close();
                        writer = null;
                    }
                    #region Defensive tracing
                    else
                    {
                        //writer is not available to clean up
                    }
                    #endregion
                }
            }

            //rethrow error
            if (error != null) throw error;
        }
        #endregion

        #region Write List
        void WriteList(ListBoxItemList items, TextWriter writer, int indentLevel)
        {
            ListBoxItem child = null;
            LogEvent log = null;
            for (int i = 0; i < items.Count; i++)
            {
                child = items[i];
                log = child.Value as LogEvent;

                if (log != null)
                {
                    WriteLogItem(log, writer, indentLevel);
                }
                else
                {
                    WriteTextItem(child, writer, indentLevel);
                }

                //write sub children
                if ((child.Children != null) && (child.Children.Count > 0))
                {
                    WriteList(child.Children, writer, indentLevel + 1);
                }
                #region Defensive tracing
                else
                {
                    //no children, move to next sibling
                }
                #endregion
            }
        }
        #endregion

        #region Write Text Item
        void WriteTextItem(ListBoxItem item, TextWriter writer, int indentLevel)
        {
            //write header
            writer.Write("          " + "          \t");
            //write indentation
            WriteIdent(writer, indentLevel);

            //write message
            writer.WriteLine(item.Text);
        }
        #endregion

        #region Write Log Item
        void WriteLogItem(LogEvent log, TextWriter writer, int indentLevel)
        {
            //write timestamp
            DateTime t = log.Time;
            writer.Write(string.Format("{0}:{1}:{2}.{3}", t.Hour, t.Minute, t.Second, t.Millisecond));

            //write log type
            if (log is InformationLog) writer.Write(" INFO    \t");
            else if (log is WarningLog) writer.Write(" WARNING \t");
            else if (log is ErrorLog) writer.Write(" ERROR   \t");
            else if (log is DebugLog) writer.Write(" DEBUG   \t");
            else writer.Write(log.GetType().Name + " \t");

            //write indentation
            WriteIdent(writer, indentLevel);

            //write message
            writer.WriteLine(log.Description);

            //write stack
            if (log.CallingStack != null) writer.WriteLine(log.CallingStack.ToString());
        }
        #endregion

        #region Write Indent
        void WriteIdent(TextWriter writer, int indentLevel)
        {
            for (int i = 0; i < indentLevel; i++)
            {
                writer.Write("     ");
            }
        }
        #endregion

        #endregion

        #region Stop
        /// <summary>
        /// Temporarily stop listening for log events. New logs will be discarded.
        /// </summary>
        public void Stop()
        {
            mIsListening = false;
        }
        #endregion

        #region Resume
        /// <summary>
        /// Continue handling log events.
        /// </summary>
        public void Resume()
        {
            mIsListening = true;
        }
        #endregion

        #region Clear All
        /// <summary>
        /// Remove all visible logs.
        /// </summary>
        public void ClearAll()
        {
            if (InvokeRequired)
            {
                EventHandler clearall = new EventHandler(ProcessClearAll);
                this.Invoke(clearall, this, EventArgs.Empty);
            }
            else ProcessClearAll(this, EventArgs.Empty);
        }

        void ProcessClearAll(object sender, EventArgs e)
        {
            lock (mSyncRoot)
            {
                Items.Clear();
                Invalidate();
            }
        }
        #endregion

        #region On Double Click
        protected override void OnDoubleClick(EventArgs e)
        {
            base.OnDoubleClick(e);

            try
            {
                //ListboxItem item = lst.Items[lst.SelectedIndex];
                //LogItem log = (LogItem)item.Value;
                ListBoxItem item = this.SelectedItem;
                LogEvent log = item.Value as LogEvent;
                //LogItem log = lst.SelectedValue as LogItem;
                if (log != null)
                {
                    if (log is ErrorLog)
                    {
                        ErrorDialog dlg = new ErrorDialog(((ErrorLog)log).Error);
                        dlg.ShowDialog();
                    }
                    //else MessageDialog.Show(log.Description);
                    else LogDialog.Show(log);
                }
                //else MessageDialog.Show(log.Description);
                else LogDialog.Show(log);
            }
            catch (Exception ex)
            {
                int tmp = 0;
            }
        }
        #endregion

        #region Context Menu

        private void mnuStop_Click(object sender, EventArgs e)
        {
            if (IsListening)
            {
                Stop();
                mnuStop.Text = "Resume";
            }
            else
            {
                Resume();
                mnuStop.Text = "Stop";
            }
        }

        private void mnuClear_Click(object sender, EventArgs e)
        {
            ClearAll();
        }

        private void mnuSave_Click(object sender, EventArgs e)
        {
            SaveToFile();
        }
        #endregion

        #region Initialize
        void Initialize()
        {
            if (Columns.Count == 0) PopulateColumns();
            AutoSizedColumn = 0;
            DrawTitle = true;
            mAddHandler = new AddItemHandler(ProcessAddItem);

            //bind
            Log.Bind(this);
        }
        #endregion

        #region ILogListener Members

        #region Receive Log
        /// <summary>
        /// Receive a broadcasted log
        /// </summary>
        void ILogListener.ReceiveLog(LogEvent log)
        {
            if (mIsListening)
            {
                lock (mSyncRoot)
                {
                    ListBoxItem item = new ListBoxItem();
                    PopulateRow(item, log);

                    if (mHasScope)
                    {
                        if (mCurrent != null)
                        {
                            if (mCurrent.Children == null) mCurrent.Children = new ListBoxItemList();
                            mCurrent.Children.Add(item);
                        }
                    }
                    else AddItem(item);
                }
            }
            #region Defensive tracing
            else
            {
                //not in listen mode, discard event.
            }
            #endregion
        }
        #endregion

        #region New Session
        /// <summary>
        /// Begins a new log session
        /// </summary>
        void ILogListener.NewSession()
        {
            ClearAll();
        }
        #endregion

        #region Start
        /// <summary>
        /// Begins a new logging scope
        /// </summary>
        /// <param name="scopeName">name of scope</param>
        /// <param name="scopeID">ID of scope</param>
        void ILogListener.Start(string scopeName, object scopeID)
        {
            if (mIsListening)
            {
                lock (mSyncRoot)
                {
                    ListBoxItem item = new ListBoxItem();
                    item[colDesc] = scopeName;
                    DateTime t = DateTime.Now;
                    item[colTime] = string.Format("{0:00}:{1:00}:{2:00}.{3:000}", t.Hour, t.Minute, t.Second, t.Millisecond);

                    if (mHasScope)
                    {
                        if (mCurrent != null)
                        {
                            if (mCurrent.Children == null) mCurrent.Children = new ListBoxItemList();
                            mCurrent.Children.Add(item);
                        }
                    }

                    mHasScope = true;
                    mCurrent = item;
                    if (!mScopeById.Contains(scopeID)) mScopeById.Add(scopeID, item);
                }
            }
            #region Defensive tracing
            else
            {
                //not in listen mode, discard event.
            }
            #endregion
        }
        #endregion

        #region End
        /// <summary>
        /// Ends a logging scope
        /// </summary>
        /// <param name="scopeID">ID of scope to end</param>
        void ILogListener.End(object scopeID)
        {
            if (mIsListening)
            {
                lock (mSyncRoot)
                {
                    if (mScopeById.Contains(scopeID))
                    {
                        ListBoxItem item = mScopeById[scopeID] as ListBoxItem;
                        if (item != null)
                        {
                            mCurrent = item.Parent;
                            if (mCurrent == null)
                            {//root scope

                                mHasScope = false;
                                AddItem(item);
                                mScopeById.Clear();
                            }
                        }
                    }
                }
            }
            #region Defensive tracing
            else
            {
                //not in listen mode, discard event.
            }
            #endregion
        }
        #endregion

        #region Update
        /// <summary>
        /// Change the name of a logging scope
        /// </summary>
        /// <param name="scopeID">ID of scope to change</param>
        /// <param name="scopeName">new scope name</param>
        void ILogListener.Update(object scopeID, string scopeName)
        {
            if (mIsListening)
            {
                lock (mSyncRoot)
                {
                    if (mScopeById.Contains(scopeID))
                    {
                        ListBoxItem item = mScopeById[scopeID] as ListBoxItem;
                        if (item != null) item[colDesc] = scopeName;
                    }
                }
            }
            #region Defensive tracing
            else
            {
                //not in listen mode, discard event.
            }
            #endregion
        }
        #endregion

        #endregion

        #region Initialize Component
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.mnuContext = new System.Windows.Forms.ContextMenuStrip(this.components);
            this.mnuStop = new System.Windows.Forms.ToolStripMenuItem();
            this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();
            this.mnuClear = new System.Windows.Forms.ToolStripMenuItem();
            this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
            this.mnuSave = new System.Windows.Forms.ToolStripMenuItem();
            this.mnuContext.SuspendLayout();
            this.SuspendLayout();
            // 
            // mnuContext
            // 
            this.mnuContext.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.mnuStop,
            this.toolStripMenuItem1,
            this.mnuClear,
            this.toolStripMenuItem2,
            this.mnuSave});
            this.mnuContext.Name = "mnuContext";
            this.mnuContext.Size = new System.Drawing.Size(132, 82);
            // 
            // mnuStop
            // 
            this.mnuStop.Name = "mnuStop";
            this.mnuStop.Size = new System.Drawing.Size(152, 22);
            this.mnuStop.Text = "Stop";
            this.mnuStop.Click += new System.EventHandler(this.mnuStop_Click);
            // 
            // toolStripMenuItem1
            // 
            this.toolStripMenuItem1.Name = "toolStripMenuItem1";
            this.toolStripMenuItem1.Size = new System.Drawing.Size(149, 6);
            // 
            // mnuClear
            // 
            this.mnuClear.Name = "mnuClear";
            this.mnuClear.Size = new System.Drawing.Size(152, 22);
            this.mnuClear.Text = "Clear all";
            this.mnuClear.Click += new System.EventHandler(this.mnuClear_Click);
            // 
            // toolStripMenuItem2
            // 
            this.toolStripMenuItem2.Name = "toolStripMenuItem2";
            this.toolStripMenuItem2.Size = new System.Drawing.Size(149, 6);
            // 
            // mnuSave
            // 
            this.mnuSave.Name = "mnuSave";
            this.mnuSave.Size = new System.Drawing.Size(152, 22);
            this.mnuSave.Text = "Save to file";
            this.mnuSave.Click += new System.EventHandler(this.mnuSave_Click);
            // 
            // LogPanel
            // 
            this.ContextMenuStrip = this.mnuContext;
            this.Name = "LogPanel";
            this.mnuContext.ResumeLayout(false);
            this.ResumeLayout(false);

        }
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        public LogPanel()
        {
            if (!DesignMode)
            {
                Initialize();
                InitializeComponent();
            }
            #region Defensive tracing
            else
            {
                //in design mode
            }
            #endregion
        }
        #endregion
    }

    delegate void AddItemHandler(ListBoxItem item);
}
